/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.ir.declarations

import org.jetbrains.kotlin.CompilerVersionOfApiDeprecation
import org.jetbrains.kotlin.DeprecatedForRemovalCompilerApi
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.transformIfNeeded
import org.jetbrains.kotlin.ir.util.transformInPlace
import org.jetbrains.kotlin.ir.visitors.IrTransformer
import org.jetbrains.kotlin.ir.visitors.IrVisitor
import org.jetbrains.kotlin.utils.addIfNotNull

// This class is not autogenerated to for the sake refactoring IR parameters - see KT-68003.
// However, it must be kept in sync with [org.jetbrains.kotlin.ir.generator.IrTree.function].
sealed class IrFunction : IrDeclarationBase(), IrPossiblyExternalDeclaration, IrDeclarationWithVisibility, IrTypeParametersContainer,
    IrSymbolOwner, IrDeclarationParent, IrReturnTarget, IrMemberWithContainerSource, IrMetadataSourceOwner {
    @ObsoleteDescriptorBasedAPI
    abstract override val descriptor: FunctionDescriptor

    abstract override val symbol: IrFunctionSymbol

    abstract var isInline: Boolean

    abstract var isExpect: Boolean

    abstract var returnType: IrType


    private val _parameters: MutableList<IrValueParameter> = ArrayList()

    /**
     * All value parameters.
     *
     * Parameters must follow this order:
     *
     * [[dispatch receiver, context parameters, extension receiver, regular parameters]].
     */
    @OptIn(DelicateIrParameterIndexSetter::class)
    var parameters: List<IrValueParameter>
        get() = _parameters
        set(value) {
            val newParameters = value.toList()

            val parameters = _parameters
            for (parameter in parameters) {
                @OptIn(DeprecatedForRemovalCompilerApi::class)
                parameter.indexInOldValueParameters = -1
                parameter.indexInParameters = -1
            }

            var newContextParametersCount = 0
            var oldIndex = 0
            for ((index, parameter) in newParameters.withIndex()) {
                val kind = requireNotNull(parameter._kind) { "Kind must be set explicitly when adding a parameter" }

                parameter.indexInParameters = index
                @OptIn(DeprecatedForRemovalCompilerApi::class)
                parameter.indexInOldValueParameters = when (kind) {
                    IrParameterKind.DispatchReceiver, IrParameterKind.ExtensionReceiver -> -1
                    IrParameterKind.Context, IrParameterKind.Regular -> oldIndex++
                }

                if (kind == IrParameterKind.Context) {
                    newContextParametersCount++
                }
            }

            parameters.clear()
            parameters.addAll(newParameters)
            _contextReceiverParametersCount = newContextParametersCount
        }

    /**
     * The first parameter of kind [IrParameterKind.DispatchReceiver] in [parameters], if present.
     */
    var dispatchReceiverParameter: IrValueParameter?
        get() = _parameters.firstOrNull { it.kind == IrParameterKind.DispatchReceiver }
        /**
         * ##### This is a deprecated API
         * Modify the [parameters] list directly.
         *
         * See [Parameter API migration guide](/docs/backend/IR_parameter_api_migration.md)
         */
        @DeprecatedForRemovalCompilerApi(CompilerVersionOfApiDeprecation._2_1_20)
        set(value) {
            setReceiverParameter(IrParameterKind.DispatchReceiver, value)
        }

    /**
     * The first parameter of kind [IrParameterKind.ExtensionReceiver] in [parameters], if present.
     *
     * ##### This is a deprecated API
     * Drop-in replacement (discouraged):
     * ```kotlin
     * parameters.firstOrNull { it.kind == IrParameterKind.ExtensionReceiver }
     * ```
     *
     * See docs/backend/IR_parameter_api_migration.md
     */
    @DeprecatedForRemovalCompilerApi(CompilerVersionOfApiDeprecation._2_1_20)
    var extensionReceiverParameter: IrValueParameter?
        get() = _parameters.firstOrNull { it.kind == IrParameterKind.ExtensionReceiver }
        set(value) {
            setReceiverParameter(IrParameterKind.ExtensionReceiver, value)
        }

    @OptIn(DelicateIrParameterIndexSetter::class)
    private fun setReceiverParameter(kind: IrParameterKind, value: IrValueParameter?) {
        val parameters = _parameters

        var index = parameters.indexOfFirst { it.kind == kind }
        var reindexSubsequent = false
        if (index >= 0) {
            val old = parameters[index]
            @OptIn(DeprecatedForRemovalCompilerApi::class)
            old.indexInOldValueParameters = -1
            old.indexInParameters = -1
            old._kind = null

            if (value != null) {
                parameters[index] = value
            } else {
                parameters.removeAt(index)
                reindexSubsequent = true
            }
        } else {
            if (value != null) {
                index = parameters.indexOfLast { it.kind < kind } + 1
                parameters.add(index, value)
                reindexSubsequent = true
            } else {
                // nothing
            }
        }

        if (value != null) {
            @OptIn(DeprecatedForRemovalCompilerApi::class)
            value.indexInOldValueParameters = -1
            value.indexInParameters = index
            value.kind = kind
        }

        if (reindexSubsequent) {
            for (i in index..<parameters.size) {
                parameters[i].indexInParameters = i
            }
        }
    }

    private var _contextReceiverParametersCount: Int = 0

    /**
     * The number of context parameters in the [valueParameters] list.
     *
     * There first `contextReceiverParametersCount` parameters in [valueParameters] are [IrParameterKind.Context],
     * the following are [IrParameterKind.Regular].
     *
     * ##### This is a deprecated API
     * Instead, use [parameters] directly. A drop-in replacement (discouraged):
     * ```
     * parameters.count { it.kind == IrParameterKind.Context }
     * ```
     *
     * See docs/backend/IR_parameter_api_migration.md
     */
    @DeprecatedForRemovalCompilerApi(CompilerVersionOfApiDeprecation._2_1_20)
    var contextReceiverParametersCount: Int
        get() = _contextReceiverParametersCount
        set(value) {
            if (value == _contextReceiverParametersCount) {
                return
            }

            replaceRegularAndExtensionParameters(null, value)
            _contextReceiverParametersCount = value
        }

    /**
     * A filtered list of [parameters], that contains only
     * [IrParameterKind.Context] and [IrParameterKind.Regular] parameters.
     * Setting this property, likewise, only replaces those kinds of parameters with the provided list,
     * leaving dispatch and extension receiver intact.
     *
     * ##### This is a deprecated API
     * Use [parameters] instead. A drop-in replacement (discouraged):
     * ```
     * parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }
     * ```
     *
     * See docs/backend/IR_parameter_api_migration.md
     */
    @DeprecatedForRemovalCompilerApi(CompilerVersionOfApiDeprecation._2_1_20)
    var valueParameters: List<IrValueParameter>
        get() = _parameters.filter { it.kind == IrParameterKind.Regular || it.kind == IrParameterKind.Context }
        set(value) {
            replaceRegularAndExtensionParameters(value, _contextReceiverParametersCount)
        }

    @OptIn(DelicateIrParameterIndexSetter::class)
    private fun replaceRegularAndExtensionParameters(newValueParameters: List<IrValueParameter>?, newContextParametersCount: Int) {
        val parameters = _parameters

        if (newValueParameters != null) {
            for (param in newValueParameters) {
                if (param._kind.let { it == IrParameterKind.DispatchReceiver || it == IrParameterKind.ExtensionReceiver }) {
                    if (param === parameters.getOrNull(param.indexInParameters)) {
                        throw IllegalArgumentException(
                            "Adding a value parameter ${param.render()} to function ${this.render()}, when it's already present as a receiver.\n" +
                                    "This operation is not supported by the old<->new parameter API bridge."
                        )
                    }
                }
            }
        }

        var dispatchReceiver: IrValueParameter? = null
        var extensionReceiver: IrValueParameter? = null
        val oldValueParameters = if (newValueParameters == null)
            ArrayList<IrValueParameter>(parameters.size)
        else null

        for (param in parameters) {
            when (param.kind) {
                IrParameterKind.DispatchReceiver -> dispatchReceiver = param
                IrParameterKind.ExtensionReceiver -> extensionReceiver = param
                else -> {
                    oldValueParameters?.add(param)
                    param.indexInParameters = -1
                    @OptIn(DeprecatedForRemovalCompilerApi::class)
                    param.indexInOldValueParameters = -1
                    param._kind = null
                }
            }
        }

        val valueParameters = newValueParameters ?: oldValueParameters!!
        val actualContextParameterCount = newContextParametersCount.coerceAtMost(valueParameters.size)

        parameters.clear()
        parameters.addIfNotNull(dispatchReceiver)
        for (i in 0..<actualContextParameterCount) {
            val param = valueParameters[i]
            parameters += param
            @OptIn(DeprecatedForRemovalCompilerApi::class)
            param.indexInOldValueParameters = i
            param.kind = IrParameterKind.Context
        }
        parameters.addIfNotNull(extensionReceiver)
        for (i in actualContextParameterCount..<valueParameters.size) {
            val param = valueParameters[i]
            parameters += param
            @OptIn(DeprecatedForRemovalCompilerApi::class)
            param.indexInOldValueParameters = i
            param.kind = IrParameterKind.Regular
        }

        for ((i, param) in parameters.withIndex()) {
            param.indexInParameters = i
        }
    }

    @OptIn(DelicateIrParameterIndexSetter::class)
    internal fun reindexValueParameters() {
        var indexInOldValueParameters = 0
        for (param in _parameters) {
            @OptIn(DeprecatedForRemovalCompilerApi::class)
            param.indexInOldValueParameters = when (param._kind) {
                null -> -1
                IrParameterKind.DispatchReceiver, IrParameterKind.ExtensionReceiver -> -1
                IrParameterKind.Context, IrParameterKind.Regular -> indexInOldValueParameters++
            }
        }
    }


    abstract var body: IrBody?

    override fun <D> acceptChildren(visitor: IrVisitor<Unit, D>, data: D) {
        typeParameters.forEach { it.accept(visitor, data) }
        _parameters.forEach { it.accept(visitor, data) }
        body?.accept(visitor, data)
    }

    override fun <D> transformChildren(transformer: IrTransformer<D>, data: D) {
        typeParameters = typeParameters.transformIfNeeded(transformer, data)
        _parameters.transformInPlace(transformer, data)
        body = body?.transform(transformer, data)
    }
}

